#! /usr/bin/env python
#coding=utf-8

"""

    2011 Boris Tatarintsev

    Python and PyGame implementation of well-known dotter game
    which is available for ios/android devices.

"""

import pygame
import pygame.locals
import sys
import os
import consts
import configreader
import time
import random
import tools
import menu
import particles
import strings
import eztext

from hsdrawer import HSDrawer
from hslocal import HSLocal
from math import sin, cos, pi


class Game(object):

    def __init__(self):
        
        self.mScreenFall = None
        # we begin with impossible state ;)
        self.mState = -1
        # init config reader
        self.mCR = configreader.ConfigReader()
        # read game settings        
        self.mIsReducedEffects = self.mCR.get('Effects', 'ReduceEffects')        
        self.mIsCheatsEnabled = self.mCR.get('Gameplay', 'Cheats')
        self.mSndPlayer = tools.getSoundPlayer(self.mCR.get('Sound', 'Sound'))
        # init pygame
        self.initPyGame(consts.SCREEN_WIDTH, consts.SCREEN_HEIGHT)
        # init sounds
        self.initSounds()
        # init sprites and menu
        self.initSprites()
        self.initMenu()
        # init high scores stuff
        self.mHSManager = HSLocal()        
        self.mHSManager.loadScoresData()
        self.mHSDrawer = HSDrawer(self.mHSManager)
        
    def initPyGame(self, scrWidth, scrHeight):
        
        # initialize game window
        pygame.init()
        # init display
        self.mScrWidth, self.mScrHeight = scrWidth, scrHeight
        self.mWindow = pygame.display.set_mode((self.mScrWidth, self.mScrHeight))
        self.mSurface = pygame.display.get_surface()
        # init caption and icon
        pygame.display.set_icon(pygame.image.load(os.path.join('..', 'icon', 'icon.gif'))) 
        pygame.display.set_caption('Python Dotter v1.0 (beta)')
        # init fonts
        fontPath = os.path.join('..', 'font', 'freesansbold.ttf')
        self.bigfont = pygame.font.Font(fontPath, 40)
        self.medfont = pygame.font.Font(fontPath, 20)
        self.smallfont = pygame.font.Font(fontPath, 13)
        self.tinyfont = pygame.font.Font(fontPath, 12) 
        self.microfont = pygame.font.Font(fontPath, 7)        
    
    def loadImagesList(self, imgList):
        
        output = {}
        idx = 0
        for item in imgList:
            output[idx] = pygame.image.load(item)
            idx += 1
        return output

    def initSounds(self):
        
        pygame.mixer.init(44100, -16, 1, 1024)
        self.mSounds = {}
        self.mSounds['click'] = pygame.mixer.Sound(os.path.join("..", "sound", "click.wav"))
        self.mSounds['wrong'] = pygame.mixer.Sound(os.path.join("..", "sound", "wrong.wav"))
        self.mSounds['collide'] = pygame.mixer.Sound(os.path.join("..", "sound", "collide.wav"))
        self.mSounds['siren'] = pygame.mixer.Sound(os.path.join("..", "sound", "siren.wav"))
        self.mSounds['gameover'] = pygame.mixer.Sound(os.path.join("..", "sound", "gameover.wav"))
        self.mSounds['pop'] = pygame.mixer.Sound(os.path.join("..", "sound", "pop.wav"))
        self.mSounds['applause'] = pygame.mixer.Sound(os.path.join("..", "sound", "applause.wav"))
    
    def initSprites(self):
        
        # load images
        self.mImgVBar = pygame.image.load(os.path.join('..', 'png', 'vert_bar.png'))        
        
        # load dots images        
        self.mImgDots = self.loadImagesList(tools.getFilesByMask(os.path.join('..', 'png'), 'dot*.png'))
        
        # cursors
        self.mImgSmallShadow = pygame.image.load(os.path.join('..', 'png', 'shadow_small.png'))
        self.mImgBigShadow = pygame.image.load(os.path.join('..', 'png', 'shadow_big.png'))
        self.mImgSmallShadow.set_alpha(50)
        self.mImgBigShadow.set_alpha(50)

        # load arrows images
        self.mArrowImgs = {}
        self.mArrowImgs[consts.DIRECT_LEFT] = pygame.image.load(os.path.join('..', 'png', 'arrow_left.png'))
        self.mArrowImgs[consts.DIRECT_RIGHT] = pygame.image.load(os.path.join('..', 'png', 'arrow_right.png'))
        self.mArrowImgs[consts.DIRECT_UP] = pygame.image.load(os.path.join('..', 'png', 'arrow_up.png'))
        self.mArrowImgs[consts.DIRECT_DOWN] = pygame.image.load(os.path.join('..', 'png', 'arrow_down.png'))
                        
        # scale all images down for hud
        self.mArrowImgsHUD = {}
        self.mArrowImgsHUD[consts.DIRECT_LEFT] = pygame.transform.scale(self.mArrowImgs[consts.DIRECT_LEFT], (20, 21))
        self.mArrowImgsHUD[consts.DIRECT_RIGHT] = pygame.transform.scale(self.mArrowImgs[consts.DIRECT_RIGHT], (20, 21))
        self.mArrowImgsHUD[consts.DIRECT_UP] = pygame.transform.scale(self.mArrowImgs[consts.DIRECT_UP], (20, 21))
        self.mArrowImgsHUD[consts.DIRECT_DOWN] = pygame.transform.scale(self.mArrowImgs[consts.DIRECT_DOWN], (20, 21))
        
        # load stars images
        self.mImgStarFull = pygame.image.load(os.path.join('..', 'png', 'star_full.png'))
        self.mImgStarEmpty = pygame.image.load(os.path.join('..', 'png', 'star_empty.png'))
        
        # load backgrounds
        self.mBacks = {}
        self.mBacks = self.loadImagesList(tools.getFilesByMask(os.path.join('..', 'png', 'bg'), 'bg*.png'))
        for j in range(len(self.mBacks)):
            self.mBacks[j].set_alpha(consts.BG_MAX_ALPHA)
        
        self.mDynObjsGroup = pygame.sprite.Group()

    def initMenu(self):
        
        # create main menu
        self.mMainMenu = menu.RotatingMenu(x = 320, y = 240, radius = 220, arc = pi, defaultAngle = pi / 2.0, drawBg = not self.mIsReducedEffects)
        self.mMainMenu.addItem(menu.MenuItem(strings.STR_MAIN_MENU_STANDART))
        self.mMainMenu.addItem(menu.MenuItem(strings.STR_MAIN_MENU_SURVIVE))
        self.mMainMenu.addItem(menu.MenuItem(strings.STR_MAIN_MENU_HSCORE))
        # this will be implemented in final release
        #self.mMainMenu.addItem(menu.MenuItem(strings.STR_MAIN_MENU_ABOUT))
        self.mMainMenu.addItem(menu.MenuItem(strings.STR_MAIN_MENU_EXIT))
        self.mMainMenu.selectItem(0)        
        # create score menu
        self.mScoreMenu = menu.RotatingMenu(x = 320, y = 240, radius = 200, arc = pi, defaultAngle = pi / 2.0)
        self.mScoreMenu.addItem(menu.MenuItem(strings.STR_MAIN_MENU_STANDART))
        self.mScoreMenu.addItem(menu.MenuItem(strings.STR_MAIN_MENU_SURVIVE))
        self.mScoreMenu.addItem(menu.MenuItem(strings.STR_MENU_BACK))
        self.mScoreMenu.selectItem(0)        
        
    def startNewGame(self, gameType = strings.STR_MAIN_MENU_STANDART):
        
        # stop all sounds
        pygame.mixer.stop()
        
        self.mDynObjsGroup = pygame.sprite.Group()        
        self.mFlashInterp = self.getFlashInterp()
        self.mScreenFall = tools.getFallEffectDrawer(self.mSurface, self.mScrWidth, self.mScrHeight)        
        
        self.mNameBox = eztext.Input(maxlength=6, color=(0,0,0), prompt='You\'ve got a highscore!', font=pygame.font.Font(None, 25), \
                                    x = self.mScrWidth / 2 - 130, y = self.mScrHeight / 2 - 30)
        
        self.reinitParticles()
        
        self.mNumDots = 0
        self.mLives = consts.MAX_LIVES
        self.mScore = 0
        self.mScoreTreshold = 0
        self.mGameType = gameType
        self.mSimulateBreakup = False
        self.mWasCollision = False
        
        # [timers for survival mode >]
        self.mTotalTime = consts.SURVIVAL_THINK_TIME_MS
        self.mTimeLeft = consts.SURVIVAL_THINK_TIME_MS
        # [<]
        
        self.changeState(consts.STATE_PLAY)

        self.mBGChangeTime = pygame.time.get_ticks()
        self.mGameTime = pygame.time.get_ticks()
        self.mColorIdx = 0
        
        self.mBackIdx = random.randint(0, len(self.mBacks) - 1)
        
        # make all backgrounds visible
        for bkgImg in self.mBacks.values():
            bkgImg.set_alpha(consts.BG_MAX_ALPHA)
        
        self.mNewBackIdx = self.mBackIdx        
        self.mFlashScreen = False        
        self.mNextDot = self.mImgDots[random.randint(0, len(self.mImgDots) - 1)]        
        self.chooseRandomDirection()
        self.processEvents()

    def reinitParticles(self):
        self.mParticlesGroup = pygame.sprite.Group()
        particles.Particle.containers = self.mParticlesGroup
        
    def showMainMenu(self):
        
        self.mScreenFall = tools.getFallEffectDrawer(self.mSurface, self.mScrWidth, self.mScrHeight)
        self.changeState(consts.STATE_MAIN_MENU)
        self.mMainMenu.selectItem(0)
        self.processEvents()
        
    def showScoreTypeMenu(self):
        
        self.mScreenFall = tools.getFallEffectDrawer(self.mSurface, self.mScrWidth, self.mScrHeight)
        self.changeState(consts.STATE_CHOOSE_SCORE_TYPE)
        self.mScoreMenu.selectItem(0)
        self.processEvents()
        
    def getCurMenu(self):
        
        if self.mState == consts.STATE_MAIN_MENU:
            return self.mMainMenu
        elif self.mState == consts.STATE_CHOOSE_SCORE_TYPE :
            return self.mScoreMenu
            
    def showHSTable(self, scoreType = strings.STR_MAIN_MENU_STANDART):
        
        self.mScreenFall = tools.getFallEffectDrawer(self.mSurface, self.mScrWidth, self.mScrHeight)
        self.mHSDrawer.useTable(scoreType)
        self.changeState(consts.STATE_SHOW_HIGH_SCORES)

    def computeSpeed(self, dot, direction):
        
        if direction == consts.DIRECT_LEFT:            
            return -(1.0 * self.mScrWidth + dot.rect.width) / consts.DOT_TRAVEL_TIME_MS, 0
        elif direction == consts.DIRECT_RIGHT:
            return (1.0 * self.mScrWidth + dot.rect.width) / consts.DOT_TRAVEL_TIME_MS, 0
        elif direction == consts.DIRECT_UP:
            return 0, -(1.0 * self.mScrHeight +  dot.rect.height) / consts.DOT_TRAVEL_TIME_MS
        elif direction == consts.DIRECT_DOWN:
            return 0, (1.0 * self.mScrHeight +  dot.rect.height) / consts.DOT_TRAVEL_TIME_MS
    
    def reduceLives(self):
        
        pygame.mixer.stop()
        if self.mLives > 0:
            self.mLives -= 1
            self.reinitParticles()
            self.changeState(consts.STATE_PREPARE)
            self.mShutterFunc = tools.getShutterEffectDrawer(self.mSurface, self.mImgVBar, self.mScrWidth, 2000)
            self.mFlashScreen = False
            self.mTimeLeft = self.mTotalTime
    
    def getFlashInterp(self):
        return tools.getLinearInterp(100, 0, consts.SCREEN_FLASH_PERIOD)
    
    def createNewDynamicObject(self, pos):
        
        # creates a new moving dot
        newParticle = pygame.sprite.Sprite(self.mDynObjsGroup)
        newParticle.image = self.mNextDot
        newParticle.rect = newParticle.image.get_rect()
        newParticle.rect.left, newParticle.rect.top = pos[0] - (newParticle.rect.width >> 1),\
                                            pos[1] - (newParticle.rect.height >> 1)

        newParticle.coord_x, newParticle.coord_y = newParticle.rect.left, newParticle.rect.top
        newParticle.half_width = newParticle.rect.width >> 1
        newParticle.half_height = newParticle.rect.height >> 1
        newParticle.direction = self.mDirection
        newParticle.collide = False
        newParticle.live_timer = tools.getLinearInterp(0, 100, consts.COLLIDED_DOT_LIVE_TIME)
        newParticle.v_x, newParticle.v_y = self.computeSpeed(newParticle, newParticle.direction)
        
        # particles have different mass for better reflection effects        
        newParticle.mass = random.randint(10, 20) if newParticle.half_width == consts.SMALL_DOT_RADIUS else random.randint(20, 30)             
        # choose next dot size
        nextParticle = self.mImgDots[random.randint(0, len(self.mImgDots) - 1)]
        
        return newParticle, nextParticle
        
    def chooseRandomDirection(self):
        
        self.mDirection = random.randint(0, consts.DIRECT_COUNT - 1)
        self.mAnimateDirection = True
        self.mDirectionAnimationTime = consts.DIRECT_ANIM_SCALE_TIME
        
    def changeState(self, newState):
        
        # changes the state to newState, returns True if success and False otherwise
        if self.mState != newState:
            if newState == consts.STATE_ENTER_NAME:
                if self.mHSManager.isHighScore(self.mGameType, self.mScore):
                    self.mState = newState
                    self.mSndPlayer(self.mSounds['applause'])
                    return True
            else:
                self.mState = newState
                return True
        return False
    
    def testCollide(self, p1, p2, deltaMS):
        
        # test AABB - AABB intersection           
        if tools.AABB_IntersectTest(p1, p2, deltaMS)[0]:        
            # test circle-circle intersection
            res = tools.circleCircleIntersect(p1.coord_x + p1.half_width, p1.coord_y + p1.half_height, p2.coord_x + p2.half_width, p2.coord_y + p2.half_height, \
                                              p1.half_width, p2.half_width, (p1.v_x, p1.v_y), (p2.v_x, p2.v_y), deltaMS)
            if res[0]: return True, res[1]
        return False, -1
    
    def testOverlap(self, p1, p2):
        
        # test AABB - AABB intersection
        if tools.AABBAABB_Overlap(p1, p2):            
            # test circle-circle intersection            
            return tools.circlesOverlap((p1.coord_x, p1.coord_y), (p2.coord_x, p2.coord_y), (p1.rect.width >> 1), (p2.rect.width >> 1))
        return False
        
    def showExplode(self, p1, p2):
        
        # compute collision point
        R = p2.half_width + p1.half_width
        b = (p2.coord_x + p2.coord_x / R, p2.coord_y + p2.coord_y / R)
        # show explosion :)
        for i in range(consts.COLLISION_PARTICLES):
            particles.Particle(b, random.uniform(-1, 1), random.uniform(-1, 1), 0, 0.01, 3,
                            [((random.randrange(128, 256), random.randrange(128, 256), random.randrange(128, 256)),
                                (255, 255, 255), 20), ((255, 255, 255), (0, 0, 0), 10)])
                    
    def showMessage(self, in_str, centerX, centerY):
        
        # shows a rectangle with the text in it
        lbl = self.bigfont.render(in_str, 1, consts.COLOR_WHITE)
        width, height = self.bigfont.size(in_str) 
        width += 8
        pygame.draw.rect(self.mSurface, consts.COLOR_GRAY, pygame.Rect(centerX - (width >> 1), centerY - (height >> 1), width, height), 0)
        self.mSurface.blit(lbl, (centerX - (width >> 1) + 4, centerY - (height >> 1)))

    def moveParticle(self, dot, deltaMS):
        
        dot.coord_y += dot.v_y * deltaMS
        dot.coord_x += dot.v_x * deltaMS
        dot.rect.top = dot.coord_y
        dot.rect.left = dot.coord_x        
        
    def reflectCircles(self, c1, c2):
        
        new_v1, new_v2 = tools.getCirclesReflection( (c1.coord_x, c1.coord_y), (c1.v_x, c1.v_y), \
                                                     (c2.coord_x, c2.coord_y), (c2.v_x, c2.v_y) )                            
        
        axis = tools.sub( (c1.coord_x, c1.coord_y), (c2.coord_x, c2.coord_y) )
        v1_proj = tools.proj( (c1.v_x, c1.v_y), axis )
        v2_proj = tools.proj( (c2.v_x, c2.v_y), axis )
        
        v_rel = v1_proj - v2_proj
        
        mass_total = c1.mass + c2.mass        
        
        u1_mag = abs( (((c1.mass - c2.mass) * v_rel) / mass_total) - v1_proj)
        u2_mag = abs( ((2 * c1.mass * v_rel) / mass_total) - v1_proj)
        
        c1.v_x, c1.v_y = new_v1[0], new_v1[1]
        c2.v_x, c2.v_y = new_v2[0], new_v2[1]
        
        # compute new velocity magnitude
        v1_norm = tools.normalize( (c1.v_x, c1.v_y) )
        v2_norm = tools.normalize( (c2.v_x, c2.v_y) )
        
        c1.v_x, c1.v_y = tools.mul(v1_norm, u1_mag)
        c2.v_x, c2.v_y = tools.mul(v2_norm, u2_mag)
                
        c1.collide = True
        c2.collide = True

    def processMenuAction(self, menuItemID):

        # what have been chosen?
        if self.mState == consts.STATE_MAIN_MENU:
            if menuItemID == strings.STR_MAIN_MENU_STANDART:
                self.startNewGame()
            elif menuItemID == strings.STR_MAIN_MENU_SURVIVE:
                self.startNewGame(strings.STR_MAIN_MENU_SURVIVE)
            elif menuItemID == strings.STR_MAIN_MENU_HSCORE:
                self.showScoreTypeMenu()
            elif menuItemID == strings.STR_MAIN_MENU_EXIT:
                sys.exit(0)
        elif self.mState == consts.STATE_CHOOSE_SCORE_TYPE:
            if menuItemID == strings.STR_MAIN_MENU_STANDART:
                self.showHSTable(strings.STR_MAIN_MENU_STANDART)
            elif menuItemID == strings.STR_MAIN_MENU_SURVIVE:
                self.showHSTable(strings.STR_MAIN_MENU_SURVIVE)            
            elif menuItemID == strings.STR_MENU_BACK:
                self.showMainMenu()

    def processEvents(self):
        
        delta = 0        
        while True:
            
            s = pygame.time.get_ticks()
            events = pygame.event.get()
            
            for event in events:
                
                if event.type == pygame.locals.QUIT:
                    sys.exit(0)
                
                result = False
                if self.mState == consts.STATE_ENTER_NAME:
                    self.mNameBox.update(events)
                    
                elif self.mState == consts.STATE_SHOW_HIGH_SCORES:
                    result = self.mHSDrawer.processEvents(events, self)
                
                # if event was not handled elsewhere
                if not result:
                    
                    if event.type == pygame.locals.ACTIVEEVENT and (self.mState == consts.STATE_PLAY or self.mState == consts.STATE_PAUSED):
                        
                        if (event.state == 2 and event.gain == 0) or (event.state == 6 and event.gain == 0):
                            self.mShutterFunc = tools.getShutterEffectDrawer(self.mSurface, self.mImgVBar, self.mScrWidth, 2000)
                            self.changeState(consts.STATE_PAUSED)
                        elif (event.state == 2 and event.gain == 1) or (event.state == 6 and event.gain == 1):                            
                            self.changeState(consts.STATE_PLAY)
                            break
                    
                    elif event.type == pygame.KEYDOWN:
                        
                        if self.mState == consts.STATE_MAIN_MENU or self.mState == consts.STATE_CHOOSE_SCORE_TYPE:
                            if event.key == pygame.K_LEFT:
                                self.getCurMenu().selectItem(self.getCurMenu().selectedItemNumber + 1)
                            if event.key == pygame.K_RIGHT:
                                self.getCurMenu().selectItem(self.getCurMenu().selectedItemNumber - 1)
                            if event.key == pygame.K_RETURN:
                                self.processMenuAction(self.getCurMenu().getSelectedItemText())

                        if self.mState == consts.STATE_PLAY or self.mState == consts.STATE_CHOOSE_SCORE_TYPE or self.mState == consts.STATE_PAUSED \
                            or self.mState == consts.STATE_PREPARE or self.mState == consts.STATE_GAMEOVER:
                            if event.key == pygame.K_ESCAPE:
                                self.showMainMenu()
                        """
                        if self.mIsCheatsEnabled:
                            # cheats
                            if event.key == 275 and self.mState == consts.STATE_PLAY:
                                self.mScore += 100
                            if event.key == 276 and self.mState == consts.STATE_PLAY:
                                self.mLives -= 1                                        
                        """
                        if consts.__DEBUG__:
                            # debug
                            if event.key == 276 and self.mState == consts.STATE_PLAY:
                                self.mDirection = consts.DIRECT_LEFT
                            if event.key == 275 and self.mState == consts.STATE_PLAY:
                                self.mDirection = consts.DIRECT_RIGHT
                            if event.key == 273 and self.mState == consts.STATE_PLAY:
                                self.mDirection = consts.DIRECT_UP
                            if event.key == 274 and self.mState == consts.STATE_PLAY:
                                self.mDirection = consts.DIRECT_DOWN

                    elif event.type == pygame.MOUSEMOTION:
                        
                        if self.mState == consts.STATE_MAIN_MENU or self.mState == consts.STATE_CHOOSE_SCORE_TYPE:
                            m_x, m_y = event.pos
                            for item in self.getCurMenu().items:
                                metrics = item.getMetrics()
                                if m_x >= metrics[0] and m_y >= metrics[1] and m_x <= metrics[2] and m_y <= metrics[3]:
                                    self.getCurMenu().selectedItem.deselect()
                                    self.getCurMenu().selectedItem = item
                                    item.select()
                                    break

                    elif event.type == pygame.MOUSEBUTTONDOWN and event.button == consts.LEFT_MOUSE_BUTTON:
                        
                        if self.mState == consts.STATE_MAIN_MENU or self.mState == consts.STATE_CHOOSE_SCORE_TYPE:
                            # check if we clicked a mouse over any of menu items
                            m_x, m_y = event.pos
                            for item in self.getCurMenu().items:
                                metrics = item.getMetrics()
                                if m_x >= metrics[0] and m_y >= metrics[1] and m_x <= metrics[2] and m_y <= metrics[3]:
                                    self.processMenuAction(item.text)
                                    break
                        
                        elif self.mState == consts.STATE_PLAY:
                            
                            # okay, user has clicked inside the game field, create new moving sprite
                            # and start to animate it
                            if not self.mSimulateBreakup:
                                
                                newParticle, nextDot = self.createNewDynamicObject(event.pos)
                                
                                # check if user doesn't try to click over another particle
                                success = True
                                for particle in self.mDynObjsGroup:
                                    if particle != newParticle:
                                        result = self.testOverlap(particle, newParticle)
                                        if result:                                            
                                            self.mDynObjsGroup.remove(newParticle)
                                            success = False
                                            self.mSndPlayer(self.mSounds['wrong'])
                                            break
                                        
                                if success:
                                    pygame.mixer.stop()
                                    self.mSndPlayer(self.mSounds['click'])                                
                                    self.mNextDot = nextDot
                                    self.mScore += consts.SCORE_STEP
                                    
                                    if self.mScore % 500 == 0 and self.mScore != 0 and self.mScore > self.mScoreTreshold:
                                        self.mTotalTime -= consts.SURVIVAL_TIME_REDUCE_MS
                                        self.mScoreTreshold = self.mScore
                                    
                                    self.chooseRandomDirection()
                                    self.mNumDots += 1
                                    self.mFlashScreen = False
                                    self.mTimeLeft = self.mTotalTime
                                                                        
                        elif self.mState == consts.STATE_PREPARE:
                            self.changeState(consts.STATE_PLAY)
                        
                        elif self.mState == consts.STATE_GAMEOVER:
                            pygame.mixer.stop()
                            if not self.changeState(consts.STATE_ENTER_NAME):
                                self.startNewGame(self.mGameType)                                                            
                            
            self.update(delta)
            self.paint(delta)
            
            # if game runs to fast - slow it down for a little bit :]
            if delta < 10:
                time.sleep((10 - delta) / 1000.0)
            
            delta = pygame.time.get_ticks() - s

    def update(self, deltaMS):
        
        # if deltaMS is very big this can cause the collision detection failure
        # so we just split the whole update onto the smaller time frames        
        if self.mState == consts.STATE_PLAY:
            
            self.mParticlesGroup.update(deltaMS)
            
            self.mGameTime += deltaMS            
            
            if self.mGameType == strings.STR_MAIN_MENU_SURVIVE:                
                if self.mTimeLeft <= 0:
                    self.mTimeLeft = self.mTotalTime
                    self.reduceLives()
                    self.mFlashScreen = False                                    
            
            # animate direction array if needed
            if self.mAnimateDirection:            
                self.mDirectionAnimationTime -= deltaMS
                if self.mDirectionAnimationTime <= 0:
                    # disable when animation is finished
                    self.mAnimateDirection = False

            to_remove = set()
            checked = set()
            
            collidedParticles = False
            
            # first check for collisions

            for curParticle in self.mDynObjsGroup:                                
                                                    
                # check object AABBs collisions. it's fast and simple.
                # most collisions will be refused at this step without testing circle-circle intersection
                for anotherParticle in self.mDynObjsGroup:
                    if curParticle != anotherParticle and anotherParticle not in checked:
                        res = self.testCollide(curParticle, anotherParticle, deltaMS)
                        if res[0]:
                            
                            if not self.mSimulateBreakup:
                                # start simple collisions simulation
                                self.mSimulateBreakup = True
                                self.mFlashScreen = True                                    
                                self.mWasCollision = True                                
                                self.mFlashInterp = self.getFlashInterp()
                                
                            self.mSndPlayer(self.mSounds['collide'])
                            if res[1] > 2:
                                self.moveParticle(curParticle, res[1] - 1)
                            if res[1] > 2:
                                self.moveParticle(anotherParticle, res[1] - 1)
                                
                            self.showExplode(curParticle, anotherParticle)
                            self.reflectCircles(curParticle, anotherParticle)
                            
                        
                checked.add(curParticle)

            
            # move all the sprites
            for curParticle in self.mDynObjsGroup:
                                   
                self.moveParticle(curParticle, deltaMS)
                
                if not curParticle.collide:
                    
                    # check screen borders
                    if curParticle.coord_y <= -curParticle.rect.height and curParticle.direction == consts.DIRECT_UP:
                        curParticle.coord_y = self.mScrHeight + (curParticle.coord_y + curParticle.rect.height)
                    elif curParticle.coord_y >= self.mScrHeight and curParticle.direction == consts.DIRECT_DOWN:
                        curParticle.coord_y = -curParticle.rect.height + (curParticle.coord_y % self.mScrHeight)
                    elif curParticle.coord_x <= -curParticle.rect.width and curParticle.direction == consts.DIRECT_LEFT:
                        curParticle.coord_x = self.mScrWidth + (curParticle.coord_x + curParticle.rect.width)
                    elif curParticle.coord_x >= self.mScrWidth and curParticle.direction == consts.DIRECT_RIGHT:
                        curParticle.coord_x = -curParticle.rect.width + (curParticle.coord_x % self.mScrWidth)
                        
                else:
                                
                    if curParticle not in to_remove:                                            
                        collidedParticles = True                        
                        # if particle was collided and it has used all the
                        # time available for it - just destroy it!                                            
                        if curParticle.live_timer(deltaMS) >= 100:
                            to_remove.add(curParticle)
                            
                            # show nice explosion
                            b = curParticle.coord_x, curParticle.coord_y
                            for i in range(consts.SELFEXPLODE_COLLISION_PARTICLES):
                                particles.Particle(b, random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-0.01, 0.01), random.uniform(-0.01, 0.01), 3,
                                                [((random.randrange(128, 256), random.randrange(128, 256), random.randrange(128, 256)),
                                                    (random.randrange(0, 256), random.randrange(0, 256), random.randrange(0, 256)), 20),
                                                    ((random.randrange(0, 256), random.randrange(0, 256), random.randrange(0, 256)), (0, 0, 0), 10)])
                            self.mScore -= consts.SCORE_STEP
                            self.mSndPlayer(self.mSounds['pop'])
                            
                        elif curParticle.coord_y <= -curParticle.rect.height or curParticle.coord_y >= self.mScrHeight or \
                            curParticle.coord_x <= -curParticle.rect.width or curParticle.coord_x >= self.mScrWidth:
                            # if particle goes beyond the screen borders then we simply destroy it
                            to_remove.add(curParticle)
                            self.mScore -= consts.SCORE_STEP

            if to_remove:
                for item in to_remove: self.mDynObjsGroup.remove(item)
                
            if not collidedParticles:
                
                self.mSimulateBreakup = False

                # mWasCollision flag ensures that we will reduce player
                # lives only by one per collision
                if self.mWasCollision:
                    self.reduceLives()
                    self.mWasCollision = False

                if self.mGameType == strings.STR_MAIN_MENU_SURVIVE:
                    if self.mTimeLeft <= consts.SURVIVAL_FLASH_TIME and not self.mFlashScreen:
                        self.mSndPlayer(self.mSounds['siren'], -1)                        
                        self.mFlashInterp = self.getFlashInterp()                        
                        self.mFlashScreen = True
                                
                # don't count time during collision scene
                self.mTimeLeft -= deltaMS
                
                # if player doesn't have lives left, go to game over state
                if self.mLives == 0:
                    self.mShutterFunc = tools.getShutterEffectDrawer(self.mSurface, self.mImgVBar, self.mScrWidth, 2000)
                    self.changeState(consts.STATE_GAMEOVER)
                    pygame.mixer.stop()
                    self.mSndPlayer(self.mSounds['gameover'])
        
        elif self.mState == consts.STATE_MAIN_MENU:
            self.mMainMenu.update()
            
        elif self.mState == consts.STATE_CHOOSE_SCORE_TYPE:
            self.mScoreMenu.update()
            
        elif self.mState == consts.STATE_ENTER_NAME:
            if self.mNameBox.finished:
                self.mHSManager.insertScore(self.mGameType, self.mNameBox.value, self.mScore)
                self.mHSManager.saveRawData()
                self.startNewGame(self.mGameType)
    
    def drawHUD(self):
        
        # draw lives left
        x = consts.LIVES_HUD_X
        for j in range(consts.MAX_LIVES):
            tmpImg = self.mImgStarFull if j < self.mLives else self.mImgStarEmpty
            self.mSurface.blit(tmpImg, (x, consts.LIVES_HUD_Y))
            x += tmpImg.get_width()
        # draw last direction arrow
        x = self.mScrWidth - consts.DIRECT_HUD_X_OFFSET
        y = consts.DIRECT_HUD_Y
        tools.drawAligned(self.mSurface, self.mArrowImgsHUD[self.mDirection], x, y, tools.HORIZ_CENTER | tools.VERT_CENTER)        
        # draw score
        scrPosX = self.mScrWidth >> 1
        tools.drawTextAligned(self.mSurface, self.smallfont, 'Score', scrPosX, 15, (255, 255, 255), tools.HORIZ_CENTER | tools.VERT_CENTER)
        tools.drawTextAligned(self.mSurface, self.smallfont, str(self.mScore), scrPosX, 30, (255, 255, 255), tools.HORIZ_CENTER | tools.VERT_CENTER)
        # draw game type string        
        tools.drawTextAligned(self.mSurface, self.smallfont, 'Game type: ' + self.mGameType, 0, self.mScrHeight, (100, 100, 100), tools.HORIZ_LEFT | tools.VERT_BOTTOM)
        # draw timeleft in survival mode
        if self.mGameType == strings.STR_MAIN_MENU_SURVIVE:
            tools.drawTextAligned(self.mSurface, self.medfont, str((self.mTimeLeft / 1000) + 1), self.mScrWidth, self.mScrHeight, (255, 255, 255), tools.HORIZ_RIGHT | tools.VERT_BOTTOM)
            
    def drawBackground(self):
        
        # draw grid
        for y in xrange(0, self.mScrHeight, 40):            
            pygame.draw.line(self.mSurface, (55, 55, 55), (0, y), (self.mScrWidth, y), 1)
        for x in xrange(0, self.mScrWidth, 40):
            pygame.draw.line(self.mSurface, (55, 55, 55), (x, 0), (x, self.mScrHeight), 1)
        
        if not self.mIsReducedEffects:
            if self.mState == consts.STATE_PLAY:
                time_passed = self.mGameTime - self.mBGChangeTime
                a = (consts.BG_CHANGE_TIME_MS - 2 * consts.BG_FADE_TIME_MS)
                if time_passed >= a and time_passed < a + consts.BG_FADE_TIME_MS:
                    # start bg fade out transition
                    p = consts.BG_MAX_ALPHA - (consts.BG_MAX_ALPHA * (time_passed - a)) / consts.BG_FADE_TIME_MS
                    self.mBacks[self.mBackIdx].set_alpha(p)
                    while self.mNewBackIdx == self.mBackIdx:
                        self.mNewBackIdx = random.randint(0, len(self.mBacks) - 1)
                elif time_passed >= consts.BG_CHANGE_TIME_MS - consts.BG_FADE_TIME_MS and time_passed < consts.BG_CHANGE_TIME_MS:
                    # choose new random wallpaper
                    p = (consts.BG_MAX_ALPHA * (time_passed - (consts.BG_CHANGE_TIME_MS - consts.BG_FADE_TIME_MS))) / consts.BG_FADE_TIME_MS
                    self.mBackIdx = self.mNewBackIdx
                    self.mBacks[self.mBackIdx].set_alpha(p)
                elif time_passed >= consts.BG_CHANGE_TIME_MS:
                    self.mBGChangeTime = pygame.time.get_ticks()
                    self.mBacks[self.mBackIdx].set_alpha(consts.BG_MAX_ALPHA)
                
            tools.drawAligned(self.mSurface, self.mBacks[self.mBackIdx], 0, 0, tools.HORIZ_LEFT | tools.VERT_TOP)
        
    def paint(self, deltaMS):
                    
        # It may seem strange that I pass deltaMS into it, but it was done just for convenience. 

        if self.mState == consts.STATE_PLAY or self.mState == consts.STATE_PAUSED or self.mState == consts.STATE_GAMEOVER \
            or self.mState == consts.STATE_PREPARE:
                        
            self.mColorIdx = 0
            
            if self.mFlashScreen:
                if self.mState == consts.STATE_PLAY:
                    self.mColorIdx = self.mFlashInterp(deltaMS)                
                if self.mColorIdx <= 0: 
                    self.mFlashInterp = self.getFlashInterp()
                    self.mColorIdx = self.mFlashInterp(0)
                        
            self.mSurface.fill( (self.mColorIdx, 0, 0) )
            self.drawBackground()                                                

            if consts.__DEBUG__:
                # draw bounding boxes
                for obj in self.mDynObjsGroup:
                    pygame.draw.rect(self.mSurface, (255, 255, 255), pygame.Rect(obj.coord_x, obj.coord_y, obj.rect.width, obj.rect.height))

            self.mDynObjsGroup.draw(self.mSurface)

            if self.mAnimateDirection:
                
                # draw direction arrow if needed
                arrowImg = self.mArrowImgs[self.mDirection]            
                k = (1.0 * self.mDirectionAnimationTime / consts.DIRECT_ANIM_SCALE_TIME)
                k_x = arrowImg.get_width() - int(arrowImg.get_width() * k)
                k_y = arrowImg.get_height() - int(arrowImg.get_height() * k)
                tempImg = pygame.transform.scale(arrowImg, (k_x, k_y))
                tempImg.set_alpha(255 * k)
                tools.drawAligned(self.mSurface, tempImg, self.mScrWidth >> 1, self.mScrHeight >> 1, tools.HORIZ_CENTER | tools.VERT_CENTER)
            
            self.drawHUD()
            
            if self.mState == consts.STATE_PLAY:
                m_x, m_y = pygame.mouse.get_pos()
                shadow_img = self.mImgSmallShadow if (self.mNextDot.get_rect().width >> 1) == consts.SMALL_DOT_RADIUS else self.mImgBigShadow
                tools.drawAligned(self.mSurface, shadow_img, m_x, m_y, tools.HORIZ_CENTER | tools.VERT_CENTER)
                self.mParticlesGroup.draw(self.mSurface)                
            elif self.mState == consts.STATE_PAUSED:
                self.mShutterFunc(deltaMS)                
                self.showMessage('Game paused', self.mScrWidth >> 1, self.mScrHeight >> 1)
            elif self.mState == consts.STATE_GAMEOVER:
                res = self.mShutterFunc(deltaMS)
                self.showMessage('Game over', self.mScrWidth >> 1, self.mScrHeight >> 1)
                if res:
                    if not self.changeState(consts.STATE_ENTER_NAME):
                        self.startNewGame(self.mGameType)                                            
            elif self.mState == consts.STATE_PREPARE:
                self.mShutterFunc(deltaMS)
                self.showMessage('Ready?', self.mScrWidth >> 1, self.mScrHeight >> 1)
            
        elif self.mState == consts.STATE_MAIN_MENU:
            
            self.mSurface.fill( (0, 0, 0) )
            self.mMainMenu.draw(self.mSurface, self.mScrWidth, self.mScrHeight)
            
        elif self.mState == consts.STATE_CHOOSE_SCORE_TYPE:
            
            self.mSurface.fill( (0, 0, 0) )
            self.mScoreMenu.draw(self.mSurface, self.mScrWidth, self.mScrHeight)
            
        elif self.mState == consts.STATE_SHOW_HIGH_SCORES:
            
            self.mSurface.fill( (0, 0, 0) )
            self.mHSDrawer.draw(self.mSurface, self.mScrWidth, self.mScrHeight)
            
        elif self.mState == consts.STATE_ENTER_NAME:
            
            self.mNameBox.draw(self.mSurface)

        if self.mScreenFall != None:
            self.mScreenFall(deltaMS)
            
        
        pygame.display.flip()


pyDotterGame = Game()
pyDotterGame.showMainMenu()

